package com.zhome.gateway.filter.route;

import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.zhome.gateway.constants.Constants;
import com.zhome.gateway.dto.ZhomeRouteDto;
import com.zhome.gateway.enums.FilterEnum;
import com.zhome.gateway.enums.GateWayCodeEnum;
import com.zhome.gateway.enums.ZhomeHttpEnum;
import com.zhome.gateway.filter.AbstractZhomeZuulFilter;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Enumeration;

/**
 * 路由功能
 * http://175.24.122.2:8848/nacos/
 * @author cuiyh9
 * @date 2020/5/9
 */
@Slf4j
public class NacosRoutingFilter extends AbstractZhomeZuulFilter {


    @Autowired
    private RestTemplate restTemplate;

    private ProxyRequestHelper helper;

    private ZuulProperties.Host hostProperties;

    private boolean sslHostnameValidationEnabled;

    public NacosRoutingFilter(ProxyRequestHelper helper, ZuulProperties properties) {
        this.helper = helper;
        this.hostProperties = properties.getHost();
        this.sslHostnameValidationEnabled = properties.isSslHostnameValidationEnabled();
    }


    @Override
    public String filterType() {
        return Constants.ROUTE_KEY;
    }

    @Override
    public int filterOrder() {
        return FilterEnum.NacosRoutingFilterEnum.getFilterOrder();
    }

    @Override
    public boolean shouldFilter() {
        RequestContext ctx = RequestContext.getCurrentContext();
        ZhomeRouteDto zhomeRouteDto = (ZhomeRouteDto)ctx.get(Constants.RequestContextKey.CTX_X_ROUTEINFO);
        return ctx.getBoolean(Constants.RequestContextKey.CTX_X_SUCCESSOR_CAN_EXECUTE)
                && Constants.ServerType.NACOS.equals(zhomeRouteDto.getType());
    }

    @Override
    public Object run() throws ZuulException {
        try {
            RequestContext ctx = RequestContext.getCurrentContext();
            HttpServletRequest request = ctx.getRequest();

            ZhomeRouteDto zhomeRouteDto = (ZhomeRouteDto)ctx.get(Constants.RequestContextKey.CTX_X_ROUTEINFO);
            String method = zhomeRouteDto.getHttpMethod();

            ResponseEntity<String> responseEntity = null;


            if (ZhomeHttpEnum.isPostMethod(method)) {
                responseEntity = doPost(ctx, request, zhomeRouteDto);
            }
            if (ZhomeHttpEnum.isGetMethod(method)) {
                responseEntity = doGet(ctx, request, zhomeRouteDto);
            }
            setResponse(responseEntity);
        } catch (Exception e) {
            log.error("路由出现异常!requestId:%s", requestId(), e);
            setFailResponse(GateWayCodeEnum.ENDPOINT_FAIL);
        }
        return null;
    }

    public ResponseEntity<String> doPost(RequestContext ctx, HttpServletRequest request, ZhomeRouteDto zhomeRouteDto) throws Exception {
        String fullUrl = (String)ctx.get(Constants.RequestContextKey.CTX_X_FULL_URL);
        String requestBody = (String)ctx.get(Constants.RequestContextKey.CTX_X_REQUEST_BODY);

        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        Enumeration<String> headerNames =  request.getHeaderNames();
        while (headerNames != null && headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            if (!headerName.startsWith("X-")) {
                continue;
            }
            map.add(headerName, request.getHeader(headerName));
        }
        map.add(Constants.RequestHeader.X_REQUEST_ID, requestId());

        HttpHeaders headers = new HttpHeaders(map);
        HttpEntity<String> nacosRequest =
                new HttpEntity<>(requestBody, headers);

        ResponseEntity<String> responseEntity = restTemplate.postForEntity(fullUrl, nacosRequest, String.class);
        return responseEntity;
    }

    public ResponseEntity<String> doGet(RequestContext ctx, HttpServletRequest request, ZhomeRouteDto zhomeRouteDto) throws Exception {
        String fullUrl = (String)ctx.get(Constants.RequestContextKey.CTX_X_FULL_URL);
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();

        Enumeration<String> headerNames =  request.getHeaderNames();
        while (headerNames != null && headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            if (!headerName.startsWith("X-")) {
                continue;
            }
            map.add(headerName, request.getHeader(headerName));
        }
        map.add(Constants.RequestHeader.X_REQUEST_ID, requestId());

        ResponseEntity<String> responseEntity = restTemplate.getForEntity(fullUrl, String.class, map);
        return responseEntity;

    }

    protected void setResponse(ResponseEntity<String> responseEntity) throws  IOException {
        HttpHeaders httpHeaders = responseEntity.getHeaders();
        InputStream ips = responseEntity.getBody()== null ? null: new ByteArrayInputStream(responseEntity.getBody().getBytes("UTF-8"));
        this.helper.setResponse(responseEntity.getStatusCodeValue(),
                ips,
                revertHeaders(httpHeaders));
    }


    private CloseableHttpClient buildHttpClient() {
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        //将最大连接数增加到200
        cm.setMaxTotal(200);
        //将每个路由的默认最大连接数增加到20
        cm.setDefaultMaxPerRoute(20);
        CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
        return httpClient;
    }

    private RequestConfig buildRequestConfig(ZhomeRouteDto zhomeRouteDto) {
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(zhomeRouteDto.getConnectTimeout())
                .setSocketTimeout(zhomeRouteDto.getSocketTimeout())
                .build();
        return requestConfig;
    }

    private MultiValueMap<String, String> revertHeaders(HttpHeaders httpHeaders) {
        MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
        for (String name : httpHeaders.keySet()) {
            if (!map.containsKey(name)) {
                map.put(name, new ArrayList<String>());
            }
            map.get(name).addAll(httpHeaders.getValuesAsList(name));
        }
        return map;
    }

    private MultiValueMap<String, String> revertHeaders(Header[] headers) {
        MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
        for (Header header : headers) {
            String name = header.getName();
            if (!map.containsKey(name)) {
                map.put(name, new ArrayList<String>());
            }
            map.get(name).add(header.getValue());
        }
        return map;
    }
}
